﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Amf4Asmx.AmfObjects;

namespace Amf4Asmx.Web.Handlers
{
    public class AmfServiceHandler : IHttpHandler
    {
        #region IHttpHandler Members

        public bool IsReusable
        {
            get { return true; }
        }

        public static event EventHandler<UnhandledExceptionEventArgs> UnhandledSoapException;
        public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException;

        private const string SuccessSuffix = "/onResult";
        private const string FaultSuffix = "/onStatus";

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "application/x-amf";
            WebServiceData ws = ServiceUtils.GetWebServiceData(context, context.Request.FilePath);
            AmfObjects.AmfMessage message;
            using (IO.AmfReader reader = new IO.AmfReader(context.Request.InputStream))
            {
                message = reader.ReadAmfMessage();
            }

            AmfObjects.AmfMessage resultMessage = new Amf4Asmx.AmfObjects.AmfMessage();
            for (int i = 0; i < message.Body.Count; i++)
            {
                //if (i == 0)
                {
                    resultMessage.Body.Add(ProcessBody(ws, message.Headers.ToArray(), message.Body[i]));
                }
                //else
                //{
                //    resultMessage.Body.Add(new AmfBody() 
                //    {
                //        Target = message.Body[i].Response + FaultSuffix, 
                //        Content = BuildError("Endpoint only supports one body at a time", "Invalid remoting call.", "System.InvalidOperationException") 
                //    });
                //}
            }

            resultMessage.Version = message.Version;

            using (IO.AmfWriter writer = new IO.AmfWriter(context.Response.OutputStream))
            {
                writer.WriteAmfMessage(resultMessage);
            }
        }

        private AmfBody ProcessBody(WebServiceData ws, AmfHeader[] headers, AmfBody inBody)
        {
            AmfBody result = new AmfObjects.AmfBody();
            result.Response = null;
            try
            {
                System.Reflection.MethodInfo target;
                object[] args;
                if (GetTargetAndArguments(ws, inBody, out target, out args))
                {
                    object webService = ws.CreateWebService(headers, inBody);
                    object methodResult = GetFinalResult(ws, inBody, target.Invoke(webService, args), args);

                    AmfHeader[] resultHeaders = ws.GetResultHeaders(webService, inBody);

                    Dictionary<string, object> outputParameters = new Dictionary<string, object>();

                    // override headers - because we support multiple bodies and they can't share output headers, put them as part of the body
                    for (int i = 0; i < resultHeaders.Length; i++)
                    {
                        outputParameters[resultHeaders[i].Name] = resultHeaders[i].Content;
                    }
                    resultHeaders = new AmfHeader[0];
                    // end override headers

                    if (outputParameters.Count == 0)
                    {
                        result.Content = methodResult;
                    }
                    else
                    {
                        outputParameters["_"] = methodResult;
                        result.Content = outputParameters;
                    }
                    result.Target = inBody.Response + SuccessSuffix;
                }
                else
                {
                    result.Content = BuildError("Invalid remoting call.", "Invalid remoting call.", "System.InvalidOperationException");
                    result.Target = inBody.Response + FaultSuffix;

                }
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                result.Content = BuildError(ex.Message, ex.Detail, ex.GetType().FullName);
                result.Target = inBody.Response + FaultSuffix;

                if (UnhandledSoapException != null)
                {
                    UnhandledSoapException(this, new UnhandledExceptionEventArgs(ex, false));
                }
            }
            catch (Exception ex)
            {
                result.Content = BuildError("Server was unable to process request.", ex.ToString(), "UnhandledException");
                result.Target = inBody.Response + FaultSuffix;

                if (UnhandledException != null)
                {
                    UnhandledException(this, new UnhandledExceptionEventArgs(ex, false));
                }
            }

            return result;
        }

        //private NamedDictionary BuildError(string description, object details, string type)
        //{
        //    NamedDictionary result = new NamedDictionary();
        //    result.TypeName = "ErrorFault";
        //    Dictionary<string, object> error = result.Properties;
        //    error["code"] = "SERVER.PROCESSING";
        //    error["level"] = "error";
        //    error["description"] = description;
        //    error["details"] = details;
        //    error["type"] = type;
        //    return result;
        //}

        private struct FaultContent
        {
            public string code;
            public string level;
            public string description;
            public object details;
            public string type;
        }

        private FaultContent BuildError(string description, object details, string type)
        {
            FaultContent result = new FaultContent();
            result.code = "SERVER.PROCESSING";
            result.level = "error";
            result.description = description;
            result.details = details;
            result.type = type;
            return result;
        }

        private bool GetTargetAndArguments(WebServiceData ws, AmfBody inBody, out System.Reflection.MethodInfo target, out object[] args)
        {
            string targetName = inBody.Target;
            string[] parts = targetName.Split('.');
            targetName = parts[parts.Length - 1];
            inBody.Target = targetName;

            if (!ws.WebMethods.ContainsKey(targetName))
            {
                target = null;
                args = null;
                return false;
            }
            target = ws.WebMethods[targetName].Method;
            args = (object[])ws.WebMethods[targetName].Instantiate(inBody.Content);
            return true;
        }

        private object GetFinalResult(WebServiceData ws, AmfBody inBody, object returnVal, object[] args)
        {
            return ws.WebMethods[inBody.Target].BuildResult(returnVal, args);
        }

        #endregion
    }
}
